﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;

namespace ReportViewer
{
    public partial class FrmMain : Form
    {
        private enum NodeType
        {
            AllClients,
            Client,
            Destination,
            AllDestinations,
            AllVersions,
            Version
        }

        private System.Resources.ResourceManager resMan = new System.Resources.ResourceManager("ReportViewer.Localization.ReportViewerStrings", typeof(FrmMain).Assembly);
        private TreeNode _allClientsNode;
        private TreeNode _destinationsNode;
        private TreeNode _versionsNode;
        private List<String> _allDestinations = new List<string>();
        private Dictionary<Int32, String> _resultIDs = new Dictionary<int, string>();

        public FrmMain()
        {
            InitializeComponent();

            this.SetupUI();
        }

        /// <summary>
        /// Setup the UI when application start
        /// </summary>
        private void SetupUI()
        {
            this.ResetTreeview();

            foreach (DataGridViewColumn column in this.dgvClients.Columns)
            {
                column.HeaderText = this.GetLocalizedString(column.HeaderText);
            }

            foreach (DataGridViewColumn column in this.dgvPstFiles.Columns)
            {
                column.HeaderText = this.GetLocalizedString(column.HeaderText);
            }

            ToolStripMenuItem connectButton = new ToolStripMenuItem();
            connectButton.Image = Properties.Resources.Lightning;
            connectButton.Text = this.GetLocalizedString("ConnectOrReload");
            connectButton.TextAlign = ContentAlignment.MiddleCenter;
            connectButton.ForeColor = Color.White;
            connectButton.BackColor = Color.LightSlateGray;
            connectButton.Click += new EventHandler(ConnectToServer);
            this.mnuStrip.Items.Add(connectButton);
        }

        /// <summary>
        /// Query the database to get a dictionary of ResultID and their meaning
        /// </summary>
        /// <returns>A dictionary of ResultID and their meaning</returns>
        private Dictionary<Int32, String> GetResultIDs()
        {
            Dictionary<Int32, String> resultIDs = new Dictionary<int, string>();
            byte[] endOfMessage = UnicodeEncoding.UTF8.GetBytes("?EndOfMessage?");
            byte[] messageToSend = UnicodeEncoding.UTF8.GetBytes("GetResultIDs");
            Socket requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                if (requestSocket == null || !requestSocket.Connected)
                    this.Connect(ref requestSocket);

                requestSocket.Send(messageToSend);
                requestSocket.Send(endOfMessage);
            }
            catch (SocketException socketEx)
            {
                if (socketEx.SocketErrorCode == SocketError.ConnectionAborted)
                {
                    requestSocket = null;
                    this.Connect(ref requestSocket);
                    requestSocket.Send(messageToSend);
                    requestSocket.Send(endOfMessage);
                }
            }

            string fullMessage = String.Empty;
            try
            {
                byte[] answer = new byte[4096];

                while (requestSocket.Receive(answer) != 0)
                {
                    fullMessage += UnicodeEncoding.UTF8.GetString(answer);
                }
            }
            catch (Exception) { }

            foreach (String result in fullMessage.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).ToList<String>())
            {
                int index = result.IndexOf(';');
                if (index != -1)
                    resultIDs.Add(Convert.ToInt32(result.Substring(0, index)), result.Substring(index + 1));
            }

            return resultIDs;
        }

        /// <summary>
        /// Get a list of all destinations for PST files registered in the database
        /// </summary>
        private List<String> Destinations
        {
            get { return this._allDestinations; }
        }

        private void RequestReport(Socket requestSocket, string request)
        {
            byte[] endOfMessage = UnicodeEncoding.UTF8.GetBytes("?EndOfMessage?");
            byte[] messageToSend = UnicodeEncoding.UTF8.GetBytes(request);

            try
            {
                if (requestSocket == null || !requestSocket.Connected)
                    this.Connect(ref requestSocket);

                requestSocket.Send(messageToSend);
                requestSocket.Send(endOfMessage);
            }
            catch (SocketException socketEx)
            {
                if (socketEx.SocketErrorCode == SocketError.ConnectionAborted)
                {
                    requestSocket = null;
                    this.Connect(ref requestSocket);
                    requestSocket.Send(messageToSend);
                    requestSocket.Send(endOfMessage);
                }
            }

            if (requestSocket.Connected)
                this.WaitForAnswer(requestSocket, UnicodeEncoding.UTF8.GetString(messageToSend));
            else
                MessageBox.Show(this.GetLocalizedString("CannotConnectToServer"));
        }

        private void WaitForAnswer(Socket socket, string command)
        {
            StateObject asyncState = new StateObject();
            asyncState.socket = socket;
            asyncState.command = command;

            try
            { socket.BeginReceive(asyncState.buffer, 0, asyncState.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveAnswer), (object)asyncState); }
            catch (Exception) { }
        }

        private void ReceiveAnswer(IAsyncResult result)
        {
            try
            {
                StateObject asyncState = (StateObject)result.AsyncState;
                Socket clientSocket = asyncState.socket;

                int dataCount = clientSocket.EndReceive(result);

                if (dataCount > 0)
                {
                    asyncState.fullMessage += UnicodeEncoding.UTF8.GetString(asyncState.buffer, 0, dataCount);
                    clientSocket.BeginReceive(asyncState.buffer, 0, asyncState.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveAnswer), asyncState);
                }
                else
                {
                    Action UpdateAppWithAnswer = () => { UpdateUI(asyncState.command, asyncState.fullMessage); };
                    try
                    {
                        this.Invoke(UpdateAppWithAnswer);
                    }
                    catch (Exception) { }
                }
            }
            catch (Exception) { }
        }

        private void Connect(ref Socket socket)
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            socket.ReceiveTimeout = 10000;
            socket.SendTimeout = 10000;
            socket.Connect(Properties.Settings.Default.ServerName, Properties.Settings.Default.ServerPort);
        }

        private void Disconnect(Socket socket)
        {
            socket.Close();
            socket = null;
        }

        /// <summary>
        /// Delete all TreeNodes and set the "All xxx" nodes.
        /// </summary>
        private void ResetTreeview()
        {
            this._allClientsNode = new TreeNode(this.GetLocalizedString("AllClients"));
            this._allClientsNode.Tag = NodeType.AllClients;
            this._allClientsNode.Nodes.Clear();
            this._destinationsNode = new TreeNode(this.GetLocalizedString("AllDestinations"));
            this._destinationsNode.Tag = NodeType.AllDestinations;
            this._destinationsNode.Nodes.Clear();
            this._versionsNode = new TreeNode(this.GetLocalizedString("AllVersions"));
            this._versionsNode.Tag = NodeType.AllVersions;
            this._versionsNode.Nodes.Clear();

            this.trvPstBackup.Nodes[0].Nodes.Clear();
            this.trvPstBackup.Nodes[0].Nodes.Add(this._allClientsNode);
            this.trvPstBackup.Nodes[0].Nodes.Add(this._destinationsNode);
            this.trvPstBackup.Nodes[0].Nodes.Add(this._versionsNode);
        }

        /// <summary>
        /// Populate the 'Destinations' TreeNode with all destination from the database.
        /// </summary>
        private void PopulateTreeviewWithDestinations()
        {
            Socket requestSocket = null;
            this.RequestReport(requestSocket, "GetAllDestinations");
        }

        /// <summary>
        /// Populate the 'Versions' TreeNode with all Versions from the database
        /// </summary>
        private void PopulateTreeviewWithVersions()
        {
            Socket requestSocket = null;
            this.RequestReport(requestSocket, "GetAllVersions");
        }

        /// <summary>
        /// Fill the Clients DataGridview with the provided list of client.
        /// </summary>
        /// <param name="clientsList">A CSV formatted list of client to display.</param>
        private void PopulateClientsDataGridview(string clientsList)
        {
            this.dgvClients.Rows.Clear();

            List<String> clients = clientsList.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).ToList<String>();

            foreach (String client in clients)
            {
                try
                {
                    int cellIndex = 0;
                    int rowIndex = this.dgvClients.Rows.Add();

                    foreach (String property in client.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        this.dgvClients.Rows[rowIndex].Cells[cellIndex].Value = property;
                        cellIndex++;
                    }
                }
                catch (Exception) { }
            }

            if (this.dgvClients.SortedColumn != null && this.dgvClients.SortOrder != SortOrder.None)
                this.dgvClients.Sort(this.dgvClients.SortedColumn, this.dgvClients.SortOrder == SortOrder.Ascending ? ListSortDirection.Ascending : ListSortDirection.Descending);

            this.dgvClients_SelectionChanged(null, null);
            this.UpdateStatusStripBar();
        }

        /// <summary>
        /// Fill the Pst DataGridview with the provided list of Pst files
        /// </summary>
        /// <param name="pstList">A CSV formatted list of Pst files to display</param>
        private void PopulatePstFilesDataGridView(string pstList)
        {
            this.dgvPstFiles.Rows.Clear();

            List<String> pstFiles = pstList.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).ToList<String>();

            foreach (String pst in pstFiles)
            {
                try
                {
                    int cellIndex = 0;
                    int rowIndex = this.dgvPstFiles.Rows.Add();

                    foreach (String property in pst.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        if (this.dgvPstFiles.Columns[cellIndex].Name == "ResultID")
                        {
                            int resultID = 0;
                            if (Int32.TryParse(property, out resultID))
                                this.dgvPstFiles.Rows[rowIndex].Cells[cellIndex].Value = this.GetLocalizedString(this._resultIDs[resultID]);
                        }
                        else
                            switch (property)
                            {
                                case "True":
                                    this.dgvPstFiles.Rows[rowIndex].Cells[cellIndex].Value = this.GetLocalizedString("True");
                                    break;
                                case "False":
                                    this.dgvPstFiles.Rows[rowIndex].Cells[cellIndex].Value = this.GetLocalizedString("False");
                                    break;
                                case "01/01/1900 01:00:00":
                                    this.dgvPstFiles.Rows[rowIndex].Cells[cellIndex].Value = this.GetLocalizedString("Never");
                                    break;
                                default:
                                    this.dgvPstFiles.Rows[rowIndex].Cells[cellIndex].Value = property;
                                    break;
                            }

                        cellIndex++;
                    }
                }
                catch (Exception) { }
            }
        }

        /// <summary>
        /// Update the diplay of the StatusStrip Bar accordingly to the number of Clients, Selected Clients, Pst Files and Selected Pst Files
        /// </summary>
        private void UpdateStatusStripBar()
        {
            try
            {
                this.toolStripStatusClients.Text = this.GetLocalizedString("Clients") + (this.dgvClients.Rows != null ? this.dgvClients.Rows.Count : 0);
                this.toolStripStatusSelectedClients.Text = this.GetLocalizedString("SelectedClients") + (this.dgvClients.SelectedRows != null ? this.dgvClients.SelectedRows.Count : 0);
                this.toolStripStatusPstFiles.Text = this.GetLocalizedString("PstFiles") + (this.dgvPstFiles.Rows != null ? this.dgvPstFiles.Rows.Count : 0);
                this.toolStripStatusSelectedPstFiles.Text = this.GetLocalizedString("SelectedPstFiles") + (this.dgvPstFiles.SelectedRows != null ? this.dgvPstFiles.SelectedRows.Count : 0);
            }
            catch (Exception) { }
        }

        private void UpdateUI(string command, string report)
        {
            if (command.Contains(":"))
                command = command.Substring(0, command.IndexOf(':'));

            switch (command)
            {
                case "GetAllDestinations":
                    this.CreateDestinationNodes(report);
                    break;
                case "GetAllVersions":
                    this.CreateVersionNodes(report);
                    break;
                case "GetAllClientsForVersion":
                case "GetAllClientsList":
                case "GetAllClientsForDestination":
                    this.PopulateClientsDataGridview(report);
                    break;
                case "GetAllPstFilesForClientID":
                    this.PopulatePstFilesDataGridView(report);
                    break;
            }
        }

        /// <summary>
        /// Create all 'Destination' subnode under 'All Destinations' TreeNode
        /// </summary>
        /// <param name="destinations">A CSV formattedlist of all Destinations</param>
        private void CreateDestinationNodes(string destinations)
        {
            this._destinationsNode.Nodes.Clear();
            List<String> destinationsList = destinations.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).ToList<String>();
            destinationsList.Sort();

            foreach (string destination in destinationsList)
            {
                TreeNode destinationNode = new TreeNode(destination);
                destinationNode.Tag = NodeType.Destination;
                this._destinationsNode.Nodes.Add(destinationNode);
            }
        }

        /// <summary>
        /// Create all 'Version' subnode under 'All Versions' TreeNode
        /// </summary>
        /// <param name="versions">A CSV formattedlist of all Versions</param>
        private void CreateVersionNodes(string versions)
        {
            this._versionsNode.Nodes.Clear();
            List<String> versionsList = versions.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).ToList<String>();
            versionsList.Sort();

            foreach (string version in versionsList)
            {
                TreeNode versionNode = new TreeNode(version);
                versionNode.Tag = NodeType.Version;
                this._versionsNode.Nodes.Add(versionNode);
            }
        }

        /// <summary>
        /// Show all clients with a specific version
        /// </summary>
        /// <param name="version">Version of clients to display</param>
        private void ShowClientsForVersion(string version)
        {
            Socket requestSocket = null;
            this.RequestReport(requestSocket, "GetAllClientsForVersion:" + version);
        }

        /// <summary>
        /// Show all clients that have a specific destination
        /// </summary>
        /// <param name="destination">Destination of clients to display</param>
        private void ShowClientsForDestination(string destination)
        {
            Socket requestSocket = null;
            this.RequestReport(requestSocket, "GetAllClientsForDestination:" + destination);
        }

        /// <summary>
        /// Show all clients no matter their version.
        /// </summary>
        private void ShowClientsForAllVersion()
        {
            Socket requestSocket = null;
            this.RequestReport(requestSocket, "GetAllClientsList");
        }

        /// <summary>
        /// Show all Pst Files for a specific client
        /// </summary>
        /// <param name="clientID">ID of the client for which we want to display Pst Files</param>
        private void ShowPstFilesForClient(string clientID)
        {
            Socket requestSocket = null;
            this.RequestReport(requestSocket, "GetAllPstFilesForClientID:" + clientID);
        }

        /// <summary>
        /// Returns a localized string depending of the current culture.
        /// Never throws exception.
        /// </summary>
        /// <param name="unlocalizedString">String to localized.</param>
        /// <returns>The localized string.</returns>
        private string GetLocalizedString(string unlocalizedString)
        {
            string result = string.Empty;

            try
            {
                result = resMan.GetString(unlocalizedString);
                if (!string.IsNullOrEmpty(result))
                    return result;
            }
            catch (Exception) { }

            return "Missing_Localized_String_For(" + ((unlocalizedString != null) ? unlocalizedString : "null") + ")";
        }

        #region {Response to menu events}

        private void ConnectToServer(object sender, EventArgs e)
        {
            this.trvPstBackup.Enabled = false;
            this.ResetTreeview();

            this.PopulateTreeviewWithDestinations();
            this.PopulateTreeviewWithVersions();

            this._resultIDs = this.GetResultIDs();

            this.trvPstBackup.Nodes[0].Expand();
            this.trvPstBackup.Enabled = true;
        }

        private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FrmSettings frmSettings = new FrmSettings();
            frmSettings.ShowDialog();
        }

        #endregion {Response to menu events}

        #region {Response to UI events}

        private void trvPstBackup_AfterSelect(object sender, TreeViewEventArgs e)
        {
            NodeType nodeType = (NodeType)e.Node.Tag;

            switch (nodeType)
            {
                case NodeType.AllClients:
                    break;
                case NodeType.Client:
                    break;
                case NodeType.Destination:
                    this.ShowClientsForDestination(e.Node.Text);
                    break;
                case NodeType.AllDestinations:
                    e.Node.Expand();
                    break;
                case NodeType.AllVersions:
                    e.Node.Expand();
                    this.ShowClientsForAllVersion();
                    break;
                case NodeType.Version:
                    this.ShowClientsForVersion(e.Node.Text);
                    break;
                default:
#if(DEBUG)
                    MessageBox.Show("Selection of a unknown Node type : " + nodeType.ToString());
#endif
                    break;
            }
        }

        private void dgvClients_SelectionChanged(object sender, EventArgs e)
        {
            if (this.dgvClients.SelectedRows != null && this.dgvClients.SelectedRows.Count == 1 && this.dgvClients.SelectedRows[0].Cells["ClientID"].Value != null)
            {
                this.ShowPstFilesForClient(this.dgvClients.SelectedRows[0].Cells["ClientID"].Value.ToString());
            }
            else
                this.dgvPstFiles.Rows.Clear();

            this.UpdateStatusStripBar();
        }

        private void dgvPstFiles_SelectionChanged(object sender, EventArgs e)
        {
            this.UpdateStatusStripBar();
        }


        #endregion {Response to UI events}
    }

    public class StateObject
    {
        public Socket socket = null;
        public const int BufferSize = 4096;
        public byte[] buffer = new byte[BufferSize];
        public string fullMessage = String.Empty;
        public string command = String.Empty;
    }


}
